/*
 *
 *  *    Copyright 2020-2021 luter.me
 *  *
 *  *    Licensed under the Apache License, Version 2.0 (the "License");
 *  *    you may not use this file except in compliance with the License.
 *  *    You may obtain a copy of the License at
 *  *
 *  *      http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *    Unless required by applicable law or agreed to in writing, software
 *  *    distributed under the License is distributed on an "AS IS" BASIS,
 *  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *    See the License for the specific language governing permissions and
 *  *    limitations under the License.
 *
 */

package com.luter.heimdall.core.utils.crypto;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

/**
 * Pbkdf 2 util.
 *
 * @author luter
 */
public final class PBKDF2Util {
    /**
     * The constant PBKDF2_ALGORITHM.
     */
    public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";

    /**
     * 盐的长度 (SALT_BYTE_SIZE*2)
     */
    public static final int SALT_BYTE_SIZE = 32 / 4;
    /**
     * 生成密文的长度(HASH_BIT_SIZE/4)
     */
    public static final int HASH_BIT_SIZE = 128 * 2;
    /**
     * 迭代次数
     */
    public static final int PBKDF2_ITERATIONS = 1000;

    private PBKDF2Util() {
    }

    /**
     * 生成密文
     *
     * @param password the password [password(明文密码), salt(盐值)]
     * @param salt     the salt
     * @return the encrypted password
     * @throws NoSuchAlgorithmException the no such algorithm exception
     * @throws InvalidKeySpecException  the invalid key spec exception
     */
    public static String encrypt(String password, String salt) throws NoSuchAlgorithmException,
            InvalidKeySpecException {

        KeySpec spec = new PBEKeySpec(password.toCharArray(), fromHex(salt), PBKDF2_ITERATIONS, HASH_BIT_SIZE);
        SecretKeyFactory f = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
        return toHex(f.generateSecret(spec).getEncoded());
    }

    /**
     * 对输入的密码进行验证
     *
     * @param plainPassword  明文密码
     * @param cipherPassword 密文密码
     * @param salt           盐
     * @return the boolean
     * @throws NoSuchAlgorithmException the no such algorithm exception
     * @throws InvalidKeySpecException  the invalid key spec exception
     */
    public static boolean matches(String plainPassword, String cipherPassword, String salt)
            throws NoSuchAlgorithmException, InvalidKeySpecException {
        // 用相同的盐值对用户输入的密码进行加密
        String encryptedAttemptedPassword = encrypt(plainPassword, salt);
        // 把加密后的密文和原密文进行比较，相同则验证成功，否则失败
        return encryptedAttemptedPassword.equals(cipherPassword);
    }

    /**
     * 通过加密的强随机数生成盐(最后转换为16进制)
     *
     * @return the string
     * @throws NoSuchAlgorithmException the no such algorithm exception
     */
    public static String generateSalt() throws NoSuchAlgorithmException {
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        byte[] salt = new byte[SALT_BYTE_SIZE];
        random.nextBytes(salt);
        return toHex(salt);
    }


    /**
     * 十六进制字符串转二进制字符串
     *
     * @param hex the hex
     * @return the byte [ ]
     */
    private static byte[] fromHex(String hex) {
        byte[] binary = new byte[hex.length() / 2];
        for (int i = 0; i < binary.length; i++) {
            binary[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
        }
        return binary;
    }


    /**
     * 二进制字符串转十六进制字符串
     *
     * @param array the array
     * @return the string
     */
    private static String toHex(byte[] array) {
        BigInteger bi = new BigInteger(1, array);
        String hex = bi.toString(16);
        int paddingLength = (array.length * 2) - hex.length();
        if (paddingLength > 0) {
            return String.format("%0" + paddingLength + "d", 0) + hex;
        } else {
            return hex;
        }
    }

    /**
     * The entry point of application.
     *
     * @param args the input arguments
     * @throws NoSuchAlgorithmException the no such algorithm exception
     * @throws InvalidKeySpecException  the invalid key spec exception
     */
    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException {
        String plainPassword = "123456";

        String salt = PBKDF2Util.generateSalt();
        String cipherPassword = PBKDF2Util.encrypt(plainPassword, salt);

        System.out.println("原始密码:" + plainPassword);
        System.out.println("盐值:" + salt);
        System.out.println("PBKDF2加盐后的密码:" + cipherPassword);
        final boolean matches = matches(plainPassword, cipherPassword, salt);
        System.out.println(matches);
    }
}
